﻿namespace Stopwatch
{
    using System;
    using System.ComponentModel;

    public struct Time : IComparable<Time>
    {
        public static readonly Time Zero = new Time();

        public Time(int hours = 0, int minutes = 0, int seconds = 0, int subseconds = 0)
            : this()
        {
            Hours = hours;
            Minutes = minutes;
            Seconds = seconds;
            Subseconds = subseconds;
        }

        public int Hours { get; set; }

        public int Minutes { get; set; }

        public int Seconds { get; set; }

        /// <summary>
        /// 1 Subsecond equals 10 milliseconds
        /// </summary>
        public int Subseconds { get; set; }

        #region overrides

        public override string ToString()
        {
            return string.Format("{0:D2}:{1:D2}:{2:D2}.{3:D2}", Hours, Minutes, Seconds, Subseconds);
        }

        public override bool Equals(object obj)
        {
            Time other = (Time)obj;
            if (other == null)
            {
                return false;
            }

            return this == other || this.CompareTo(other) == 0;
        }

        public override int GetHashCode()
        {
            return ConverToAnd(this).GetHashCode();
        }

        #endregion

        #region IComparable<Time>

        public int CompareTo(Time other)
        {
            return (int)(ConverToAnd(this) - ConverToAnd(other));
        }

        #endregion

        #region operators

        public static bool operator <(Time t1, Time t2)
        {
            return t1.CompareTo(t2) < 0;
        }

        public static bool operator <=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) <= 0;
        }

        public static bool operator >(Time t1, Time t2)
        {
            return t1.CompareTo(t2) > 0;
        }

        public static bool operator >=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) >= 0;
        }

        public static bool operator ==(Time t1, Time t2)
        {
            return t1.CompareTo(t2) == 0;
        }

        public static bool operator !=(Time t1, Time t2)
        {
            return t1.CompareTo(t2) != 0;
        }

        public static Time operator +(Time t1, Time t2)
        {
            t1 = t1.AddSubseconds(t2.Subseconds);
            t1 = t1.AddSeconds(t2.Seconds);
            t1 = t1.AddMinutes(t2.Minutes);
            t1 = t1.AddHours(t2.Hours);

            return t1;
        }

        public static Time operator -(Time t1, Time t2)
        {
            t1 = t1.AddSubseconds(-t2.Subseconds);
            t1 = t1.AddSeconds(-t2.Seconds);
            t1 = t1.AddMinutes(-t2.Minutes);
            t1 = t1.AddHours(-t2.Hours);

            return t1;
        }

        Time AddSubseconds(int millisecond)
        {
            Subseconds += millisecond;

            int offset = Subseconds / 100;
            Subseconds %= 100;

            if (Subseconds < 0)
            {
                offset -= 1;
                Subseconds += 100;
            }

            if (offset != 0)
            {
                AddSeconds(offset);
            }

            return this;
        }

        Time AddSeconds(int seconds)
        {
            Seconds += seconds;

            int offset = Seconds / 60;
            Seconds %= 60;

            if (Seconds < 0)
            {
                offset -= 1;
                Seconds += 60;
            }

            if (offset != 0)
            {
                AddMinutes(offset);
            }

            return this;
        }

        Time AddMinutes(int minutes)
        {
            Minutes += minutes;

            int offset = Minutes / 60;
            Minutes %= 60;

            if (Minutes < 0)
            {
                offset -= 1;
                Minutes += 60;
            }

            if (offset != 0)
            {
                AddHours(offset);
            }

            return this;
        }

        Time AddHours(int hours)
        {
            Hours += hours;
            return this;
        }

        #endregion

        static long ConverToAnd(Time time)
        {
            return time.Hours * 1000 * 100 * 100 + time.Minutes * 1000 * 100 + time.Seconds * 1000 + time.Subseconds * 10;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
